package name.cyrax.red.jpv.impl.funcs.contexts;

import java.util.ArrayList;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import name.cyrax.red.jpv.api.adapters.CalculationContext;
import name.cyrax.red.jpv.api.adapters.DoubleAdapter;
import name.cyrax.red.jpv.api.adapters.IntegerAdapter;
import name.cyrax.red.jpv.api.adapters.LongAdapter;
import name.cyrax.red.jpv.api.adapters.NumericAccessor;
import name.cyrax.red.jpv.api.adapters.NumericAdapter;
import name.cyrax.red.jpv.api.adapters.PrepareAdapter;
import name.cyrax.red.jpv.api.funcs.InvocableAdapter;
import name.cyrax.red.jpv.api.funcs.InvocationContext;
import name.cyrax.red.jpv.impl.adapters.CachedDoubleImpl;
import name.cyrax.red.jpv.impl.adapters.CachedIntegerImpl;
import name.cyrax.red.jpv.impl.adapters.CachedLongImpl;

public class AnalyzeInvocationContext implements InvocationContext
{
	private final Map<NumericAdapter, CountContainer> adapters = new HashMap<>(32);
	private final List<CountContainer> sequence = new ArrayList<>(32);
	private int adapterOccurances;
	private int reusedAdapters;
	private int adapterCount;
	private int prepareableCount;
	private CountContainer lastPrepare;
	private Iterator<CountContainer> optimizeWalker;

	public NumericAdapter collect(InvocableAdapter adapter) {
		final NumericAdapter root = adapter.access(this, NumericAccessor.GET_ADAPTER);
		int idxNextSequence = sequence.size();
		analyzeAdapter(root);
		while (idxNextSequence < sequence.size()) {
			CountContainer c = sequence.get(idxNextSequence);
			NumericAdapter a = c.adapter;
			int idxParam = 0;
			while (true) {
				NumericAdapter p = a.getParam(idxParam);
				if (p == null) break;
				idxParam++;
				analyzeAdapter(p);
			}			
			idxNextSequence++;
		}
		return root;
	}
	
	public boolean isOptimizationNeeded() {
		return false;//reusedAdapters > 0;
	}

	public NumericAdapter optimize(InvocableAdapter adapter) {
		if (optimizeWalker == null) {
			lastPrepare = null; // we will recalc them
			prepareableCount = 0;
			optimizeWalker = sequence.iterator();
		}
		return adapter.access(this, NumericAccessor.GET_ADAPTER);
	}
	
	protected void analyzeAdapter(NumericAdapter adapter)
	{
		adapterOccurances++;
		CountContainer container = adapters.get(adapter);
		if (container == null) {
			adapterCount++;
			container = new CountContainer();
			container.adapter = adapter;
			adapters.put(adapter, container);
			checkPrepare(adapter, container);
		} else if (container.reuseCounter == 0) {
			container.reuseCounter = 1;
			reusedAdapters++;
		} else {
			container.reuseCounter++;
		}
		sequence.add(container);
	}
	
	private final void checkPrepare(NumericAdapter adapter, CountContainer container) {
		PrepareAdapter p = adapter.prepare();
		if (p != null) {
			prepareableCount++;
			container.prepareAdapter = p;
			container.nextPrepare = lastPrepare;
			lastPrepare = container;
		}		
	}
	
	public int getAdapterCount()
	{
		return adapterCount;
	}

	public int getAdapterOccurances()
	{
		return adapterOccurances;
	}

	public int getReusedAdapters()
	{
		return reusedAdapters;
	}

	private final static class CountContainer {
		public NumericAdapter	adapter;
		public int reuseCounter;
		public CountContainer nextPrepare;
		public NumericAdapter optimizedAdapter;
		public PrepareAdapter	prepareAdapter;
	}

	@Override public CalculationContext getCalculationContext()
	{
		return CalculationContext.NULL;
	}

	@Override public void beginCalc()
	{
		throw new UnsupportedOperationException();
	}

	@Override public void endCalc()
	{
		throw new UnsupportedOperationException();
	}
	
	private static final NumericAccessor<NumericAdapter> cachedAdapter = new NumericAccessor<NumericAdapter>() {

		@Override public NumericAdapter asInteger(IntegerAdapter adapter)
		{
			return new CachedIntegerImpl(adapter);
		}

		@Override public NumericAdapter asLong(LongAdapter adapter)
		{
			return new CachedLongImpl(adapter);
		}

		@Override public NumericAdapter asDouble(DoubleAdapter adapter)
		{
			return new CachedDoubleImpl(adapter);
		}
		
	};

	protected <N extends NumericAdapter> N optimizeAdapter(N adapter)
	{
		CountContainer container = optimizeWalker.next();
		if (container.reuseCounter > 0) {
			NumericAdapter s = container.optimizedAdapter;
			if (s == null) {
				container.adapter = adapter;
				s = adapter.detectType(cachedAdapter);
				container.optimizedAdapter = s;
				checkPrepare(s, container);
			} else {
				if (container.adapter != adapter) 
					throw new IllegalStateException(); 
			}
			@SuppressWarnings("unchecked") N r = (N) s;
			adapter = r;
		} else {
			checkPrepare(adapter, container);
		}
		
		return adapter;
	}

	public PrepareAdapter[] getPrepareAdapters() {
		if (prepareableCount <= 0) return null;
		PrepareAdapter[] result = new PrepareAdapter[prepareableCount];
		CountContainer c = lastPrepare;
		int idxResult = 0;
		while (c != null) {
			result[idxResult++] = c.prepareAdapter;
			c = c.nextPrepare;
		}
		return result ;
	}
}
